/*
 * spieeprom.c
 *
 *  Created on: Nov 11, 2013
 *      Author: Ahmed
 */

/* Includes */
#include "stm32f10x.h"
#include "spieeprom.h"

// this function initializes the SPI1 peripheral
void SPI1_configuration(void){

	GPIO_InitTypeDef GPIO_InitStruct;
	SPI_InitTypeDef SPI_InitStruct;

	// enable clock for used IO pins
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
	/* configure pins used by SPI1
	 * PA5 = SCK
	 * PA6 = MISO
	 * PA7 = MOSI
	 */
//	SCK PA5 	Alternate function push-pull (50MHz)
//	MOSI PA7 	Alternate function push-pull (50MHz)
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);

//	MISO PA6 Input floating
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);

	/* Configure the chip select pin
	   in this case we will use PA4 */
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);

	CS_HIGH(); // set CS high

	// enable peripheral clock
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

	/* configure SPI1 in Mode 0
	 * CPOL = 0 --> clock is low when idle
	 * CPHA = 0 --> data is sampled at the first edge
	 */
	SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // set to full duplex mode, separate MOSI and MISO lines
	SPI_InitStruct.SPI_Mode = SPI_Mode_Master;     // transmit in master mode, NSS pin has to be always high
	SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b; // one packet of data is 8 bits wide
	SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;        // clock is low when idle
	SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;      // data sampled at first edge

	SPI_InitStruct.SPI_NSS = SPI_NSS_Soft | SPI_NSSInternalSoft_Set; // set the NSS management to internal and pull internal NSS high
	SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; // SPI frequency is APB2 frequency / 4
	SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;// data is transmitted MSB first
	SPI_Init(SPI1, &SPI_InitStruct);

	SPI_Cmd(SPI1, ENABLE); // enable SPI1
}

/**
  * @brief  Send one or two bytes command to the EEPROM.
  * @param  Cmd: The user expected command to send to SPI EEPROM.
  * @param  Arg: The command argument, mostly the 8 bit status register.
  * @retval status (in case of read status cmd)
  *
  */
uint8_t spi_SendCmd(uint8_t Cmd, uint8_t Arg)
{
  uint8_t status = 0x00;

  CS_LOW();

  switch (Cmd) {
	case cmdWREN:
	case cmdWRDI:
		spi_WriteByte(Cmd);
		break;

	case cmdRDSR:
		spi_WriteByte(Cmd);	// send the command
		status = spi_ReadByte();	// Send a dummy byte to read the byte
		break;
	case cmdWRSR:
		spi_WriteByte(Cmd);	// send the command
		spi_WriteByte(Arg);	// send the lower byte of the 16 bit argument
		break;

	default:
		break;
  }
  CS_HIGH();
  return status;
}

/**
  * @brief  Reads a block of data from the spi EEPROM
  * @param  pBuffer: pointer to the buffer that receives the data read from the
  *                  device.
  * @param  ReadAddr: EEPROM's internal address to read from.
  * @param  Size: the EEPROM Data size.
  */
void spiEE_Read(uint8_t* pBuffer, uint16_t ReadAddr, uint16_t Size)
{
  uint32_t i = 0;
  uint8_t Frame[3];

  /*!< SD chip select low */
  CS_LOW();

  /*!< Send read command */
  Frame[0] = cmdREAD; 					/*!< Construct byte 1 */
  Frame[1] = (uint8_t)(ReadAddr >> 8); 	/*!< Construct byte 2 */
  Frame[2] = (uint8_t)(ReadAddr); 		/*!< Construct byte 3 */
  for (i = 0; i < 3; i++) {
  	spi_WriteByte(Frame[i]);
  }

  /*!< Read the block data : read NumByteToRead data */
      for (i = 0; i < Size; i++)
      {
        /*!< Save the received data */
        *pBuffer = spi_ReadByte();
        /*!< Point to the next location where the byte read will be saved */
        pBuffer++;
      }
  /*!< SD chip select high */
  CS_HIGH();
}

/**
  * @brief  Reads a block of data from the spi EEPROM
  * @param  pBuffer: pointer to the buffer that receives the data read from the
  *                  device.
  * @param  ReadAddr: EEPROM's internal address to read from.
  * @param  Size: the EEPROM Data size.
  *
  * This function does not include any delay at end of writing to allow the EEPROM to record
  */
void spiEE_WriteWithinPage(uint8_t* pBuffer, uint16_t WriteAddr, uint16_t Size)
{
  uint32_t i = 0;
  uint8_t Frame[3];

  spi_SendCmd(cmdWREN,0);		// Send Write enable command first

  /*!< SD chip select low */
  CS_LOW();

  /*!< Send write command */
  Frame[0] = cmdWRITE; 						/*!< Construct byte 1 */
  Frame[1] = (uint8_t)(WriteAddr >> 8); 	/*!< Construct byte 2 */
  Frame[2] = (uint8_t)(WriteAddr); 			/*!< Construct byte 3 */
  for (i = 0; i < 3; i++) {
  	spi_WriteByte(Frame[i]);
  }

  /*!< Write the data */
      for (i = 0; i < Size; i++)
      {
        /*!< Save the received data */
        spi_WriteByte(*pBuffer);
        /*!< Point to the next location where the byte to write next */
        pBuffer++;
      }
  /*!< SD chip select high */
  CS_HIGH();
}

/**
  * @brief  Writes buffer of data to the SPI EEPROM.
  * @param  pBuffer : pointer to the buffer  containing the data to be written
  *         to the EEPROM.
  * @param  WriteAddr : EEPROM's internal address to write to.
  * @param  NumByteToWrite : number of bytes to write to the EEPROM.
  * @retval None
  */
void spiEE_WriteAcrossPages(uint8_t* pBuffer, uint16_t WriteAddr, uint16_t NumByteToWrite)
{
  uint16_t NumOfPage = 0, NumOfSingle = 0, count = 0;
  uint16_t Addr = 0, spiEEDataNum = 0;

  Addr = WriteAddr % spiEE_PAGESIZE;
  count = spiEE_PAGESIZE - Addr;
  NumOfPage =  NumByteToWrite / spiEE_PAGESIZE;
  NumOfSingle = NumByteToWrite % spiEE_PAGESIZE;

  /*!< If WriteAddr is spiEE_PAGESIZE aligned  */
  if(Addr == 0)
  {
    /*!< If NumByteToWrite < spiEE_PAGESIZE */
    if(NumOfPage == 0)
    {
      /* Store the number of data to be written */
      spiEEDataNum = NumOfSingle;
      /* Start writing data */
      spiEE_WriteWithinPage(pBuffer, WriteAddr, spiEEDataNum);
      spiEE_WaitEepromWIP();
    }
    /*!< If NumByteToWrite > spiEE_PAGESIZE */
    else
    {
      while(NumOfPage--)
      {
        /* Store the number of data to be written */
        spiEEDataNum = spiEE_PAGESIZE;
        spiEE_WriteWithinPage(pBuffer, WriteAddr, spiEEDataNum);
        spiEE_WaitEepromWIP();
        WriteAddr +=  spiEE_PAGESIZE;
        pBuffer += spiEE_PAGESIZE;
      }

      if(NumOfSingle!=0)
      {
        /* Store the number of data to be written */
        spiEEDataNum = NumOfSingle;
        spiEE_WriteWithinPage(pBuffer, WriteAddr, spiEEDataNum);
        spiEE_WaitEepromWIP();
      }
    }
  }
  /*!< If WriteAddr is not spiEE_PAGESIZE aligned  */
  else
  {
    /*!< If NumByteToWrite < spiEE_PAGESIZE */
    if(NumOfPage== 0)
    {
      /*!< If the number of data to be written is more than the remaining space
      in the current page: */
      if (NumByteToWrite > count)
      {
        /* Store the number of data to be written */
        spiEEDataNum = count;
        /*!< Write the data contained in same page */
        spiEE_WriteWithinPage(pBuffer, WriteAddr, spiEEDataNum);
        spiEE_WaitEepromWIP();

        /* Store the number of data to be written */
        spiEEDataNum = (NumByteToWrite - count);
        /*!< Write the remaining data in the following page */
        spiEE_WriteWithinPage((uint8_t*)(pBuffer + count), (WriteAddr + count), spiEEDataNum);
        spiEE_WaitEepromWIP();
      }
      else
      {
        /* Store the number of data to be written */
        spiEEDataNum = NumOfSingle;
        spiEE_WriteWithinPage(pBuffer, WriteAddr, spiEEDataNum);
        spiEE_WaitEepromWIP();
      }
    }
    /*!< If NumByteToWrite > spiEE_PAGESIZE */
    else
    {
      NumByteToWrite -= count;
      NumOfPage =  NumByteToWrite / spiEE_PAGESIZE;
      NumOfSingle = NumByteToWrite % spiEE_PAGESIZE;

      if(count != 0)
      {
        /* Store the number of data to be written */
        spiEEDataNum = count;
        spiEE_WriteWithinPage(pBuffer, WriteAddr, spiEEDataNum);
        spiEE_WaitEepromWIP();
        WriteAddr += count;
        pBuffer += count;
      }

      while(NumOfPage--)
      {
        /* Store the number of data to be written */
        spiEEDataNum = spiEE_PAGESIZE;
        spiEE_WriteWithinPage(pBuffer, WriteAddr, spiEEDataNum);
        spiEE_WaitEepromWIP();
        WriteAddr +=  spiEE_PAGESIZE;
        pBuffer += spiEE_PAGESIZE;
      }
      if(NumOfSingle != 0)
      {
        /* Store the number of data to be written */
        spiEEDataNum = NumOfSingle;
        spiEE_WriteWithinPage(pBuffer, WriteAddr, spiEEDataNum);
        spiEE_WaitEepromWIP();
      }
    }
  }
}

void CS_LOW(void){
	GPIO_ResetBits(GPIOA,GPIO_Pin_8);
}

void CS_HIGH(void){
	GPIO_SetBits(GPIOA,GPIO_Pin_8);
}

/**
  * @brief  Write a byte to an SPI device.
  * @param  Data: byte to send.
  * @retval None
  *  <<<<<<<<<<<<<< ASSUMES CS IS HANDLED EXTERNALLY >>>>>>>>>>>>>>>>
  */
uint8_t spi_WriteByte(uint8_t Data)
{
  /*!< Wait until the transmit buffer is empty */
  while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET)
  {
  }

  /*!< Send the byte */
  SPI_I2S_SendData(SPI1, Data);

  /*!< Wait to receive a byte*/
  while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET)
  {
  }

  /*!< Return the byte read from the SPI bus */
  return (uint8_t)SPI_I2S_ReceiveData(SPI1);
}

/**
  * @brief  Read a byte from the SD.
  * @param  None
  * @retval The received byte.
  *  <<<<<<<<<<<<<< ASSUMES CS IS HANDLED EXTERNALLY >>>>>>>>>>>>>>>>
  */
uint8_t spi_ReadByte(void)
{
  uint8_t Data = 0;

  /*!< Wait until the transmit buffer is empty */
  while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET)
  {
  }
  /*!< Send the byte */
  SPI_I2S_SendData(SPI1, DUMMY_BYTE);

  /*!< Wait until a data is received */
  while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET)
  {
  }
  /*!< Get the received data */
  Data = (uint8_t)SPI_I2S_ReceiveData(SPI1);

  /*!< Return the shifted data */
  return Data;
}

/* keep reading the WIP flag in the status register till it is cleared */
void spiEE_WaitEepromWIP() {
	while (spi_SendCmd(cmdRDSR,0) & 1)	// EEPROM busy writing data
	{}
}